#include "PushButton.h"

#if (ARDUINO <= 22)
 #include <WProgram.h>
#else
 #include <Arduino.h>
#endif

PushButton
    ::PushButton(int pinNumber) :
        mPinNumber(pinNumber),
        mPolarity(ACTIVE_HIGH),
        mState(IDLE),
        mDebounceDelayMsMin(50),
        mConstantActiveDelayMsMin(1000),
        mDoubleActiveDelayMsMax(500),
        mCounterMsLast(0)
{
    // Configure pin as input
    pinMode(pinNumber, INPUT);
    // Enable (internal 20kOhm) pull-up resistor
    digitalWrite(pinNumber, HIGH);
}

PushButton
    ::PushButton(
        int pinNumber,
        int polarity) :
            mPinNumber(pinNumber),
            mPolarity(polarity),
            mState(IDLE),
            mDebounceDelayMsMin(50),
            mConstantActiveDelayMsMin(1000),
            mDoubleActiveDelayMsMax(500),
            mCounterMsLast(0)
{
    // Configure pin as input
    pinMode(pinNumber, INPUT);
    // Enable (internal 20kOhm) pull-up resistor
    digitalWrite(pinNumber, HIGH);
}

int PushButton
    ::Service(void)
{
    int _activity = NO_ACTIVITY;

    switch (mState)
    {
        case IDLE:
            {
                // Has the push button been pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    CounterReset();
                    mState = DEBOUNCE_AFTER_IDLE;
                }
            }
            break;
        case DEBOUNCE_AFTER_IDLE:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    // Debounce succesfull, goto next state
                    if (CounterExpired(mDebounceDelayMsMin))
                    {
                        mState = WAIT_AFTER_INITIAL_HIGH_TRANSITION;
                    }
                }
                else
                {
                    mState = IDLE;
                }
            }
            break;
        case WAIT_AFTER_INITIAL_HIGH_TRANSITION:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    // Held pushed constantly for a while, goto next state
                    if (CounterExpired(mConstantActiveDelayMsMin))
                    {
                        mState = PUSHED_CONSTANTLY;
                    }
                }
                else
                {
                    CounterReset();
                    mState = DEBOUNCE_AFTER_HIGH;
                }
            }
            break;
        case PUSHED_CONSTANTLY:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    _activity = CONSTANT_PUSH;
                }
                else
                {
                    mState = IDLE;
                }
            }
            break;
        case DEBOUNCE_AFTER_HIGH:
            {
                // Has the button been released?
                if (DigitalRead(mPinNumber) != mPolarity)
                {
                    // Debounce succesfull, goto next state
                    if (CounterExpired(mDebounceDelayMsMin))
                    {
                        mState = WAIT_AFTER_INITIAL_LOW_TRANSITION;
                    }
                }
                else
                {
                    CounterReset();
                    mState = WAIT_AFTER_INITIAL_HIGH_TRANSITION;
                }
            }
            break;
        case WAIT_AFTER_INITIAL_LOW_TRANSITION:
            {
                // Has the button been pushed again?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    CounterReset();
                    mState = DEBOUNCE_AFTER_LOW;
                }
                else
                {
                    if (CounterExpired(mDoubleActiveDelayMsMax))
                    {
                        _activity = SINGLE_PUSH;
                        mState = IDLE;
                    }
                }
            }
            break;
        case DEBOUNCE_AFTER_LOW:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    // Debounce succesfull, goto next state
                    if (CounterExpired(mDebounceDelayMsMin))
                    {
                        mState = WAIT_AFTER_SECONDARY_HIGH_TRANSITION;
                    }
                }
                else
                {
                    mState = WAIT_AFTER_INITIAL_LOW_TRANSITION;
                }
            }
            break;
        case WAIT_AFTER_SECONDARY_HIGH_TRANSITION:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    // Held pushed constantly for a while, goto next state
                    if (CounterExpired(mConstantActiveDelayMsMin))
                    {
                        mState = PUSHED_CONSTANTLY_SECONDARY;
                    }
                }
                else
                {
                    _activity = DOUBLE_PUSH;
                    mState = IDLE;
                }
            }
            break;
        case PUSHED_CONSTANTLY_SECONDARY:
            {
                // Is the button still pushed?
                if (DigitalRead(mPinNumber) == mPolarity)
                {
                    _activity = CONSTANT_PUSH2;
                }
                else
                {
                    mState = IDLE;
                }
            }
            break;
        default:
            {
                mState = IDLE;
            }
            break;
    }

    return _activity;
}

bool PushButton
    ::CounterExpired(unsigned long expirationDelayMs)
{
    bool _expired = false;
    
    unsigned long _counterMsNew = millis();
                    
    // Counter overflow?
    if (_counterMsNew < mCounterMsLast)
    {
        // Delay expired?
        if (((0xFFFFFFFF - mCounterMsLast) + _counterMsNew) > expirationDelayMs)
        {
            _expired = true;
        }
    }
    else
    {
        // Delay expired?
        if ((_counterMsNew - mCounterMsLast) > expirationDelayMs)
        {
            _expired = true;
        }
    }
    
    if (_expired)
    {
        // CounterReset(_counterMsNew);
        CounterReset();
    }
    
    return _expired;
}

void PushButton
    ::CounterReset(unsigned long resetValue)
{
    if (resetValue)
    {
        mCounterMsLast = resetValue;
    }
    else
    {
        mCounterMsLast = millis();
    }
}

unsigned char PushButton
    ::DigitalRead(unsigned int pinNumber)
{
    if (mOnDigitalReadCallback.member)
    {
        return mOnDigitalReadCallback.member(pinNumber, mOnDigitalReadCallback.object);
    }
    else
    {
        return digitalRead(pinNumber);
    }
}
